local addonName, addon = ...

-----------------------------------------------------------------------
-- Make sure we are prepared
--

local function print(...)
    _G.print("|cff259054BugSack:|r", ...)
end
if not LibStub then
    print("BugSack requires LibStub.")
    return
end

local L = addon.L
local BugGrabber = BugGrabber
if not BugGrabber then
    local msg = L["|cffff4411BugSack requires the |r|cff44ff44!BugGrabber|r|cffff4411 addon, which you can download from the same place you got BugSack. Happy bug hunting!|r"]
    local f = CreateFrame("Frame")
    f:SetScript("OnEvent", function()
        RaidNotice_AddMessage(RaidWarningFrame, msg, { r = 1, g = 0.3, b = 0.1 })
        print(msg)
        f:UnregisterEvent("PLAYER_ENTERING_WORLD")
        f:SetScript("OnEvent", nil)
        f = nil
    end)
    f:RegisterEvent("PLAYER_ENTERING_WORLD")
    return
end

-- We seem fine, let the world access us.
_G[addonName] = addon
addon.healthCheck = true


-- Sound
local media = LibStub("LibSharedMedia-3.0")
media:Register("sound", "BugSack: Fatality", "Interface\\AddOns\\" .. addonName .. "\\Media\\error.ogg")

-----------------------------------------------------------------------
-- Utility
--

local onError
do
    local lastError = nil
    function onError()
        if not lastError or GetTime() > (lastError + 2) then
            if not addon.db.mute then
                local sound = media:Fetch("sound", addon.db.soundMedia)
                if addon.db.useMaster then
                    PlaySoundFile(sound, "Master")
                else
                    PlaySoundFile(sound)
                end
            end
            if addon.db.chatframe then
                print(L["There's a bug in your soup!"])
            end
            lastError = GetTime()
        end
        -- If the frame is shown, we need to update it.
        if (addon.db.auto and not InCombatLockdown()) or (BugSackFrame and BugSackFrame:IsShown()) then
            addon:OpenSack()
        end
        addon:UpdateDisplay()
    end
end

-----------------------------------------------------------------------
-- Event handling
--

do
    local eventFrame = CreateFrame("Frame", nil, InterfaceOptionsFramePanelContainer)
    eventFrame:SetScript("OnEvent", function(self, event, loadedAddon)
        if loadedAddon ~= addonName then
            return
        end
        self:UnregisterEvent("ADDON_LOADED")

        local ac = LibStub("AceComm-3.0", true)
        if ac then
            ac:Embed(addon)
        end
        local as = LibStub("AceSerializer-3.0", true)
        if as then
            as:Embed(addon)
        end

        local popup = _G.StaticPopupDialogs
        if type(popup) ~= "table" then
            popup = {}
        end
        if type(popup.BugSackSendBugs) ~= "table" then
            popup.BugSackSendBugs = {
                text = L["Send all bugs from the currently viewed session (%d) in the sack to the player specified below."],
                button1 = L["Send"],
                button2 = CLOSE,
                timeout = 0,
                whileDead = true,
                hideOnEscape = true,
                hasEditBox = true,
                OnAccept = function(self, data)
                    local recipient = self.editBox:GetText()
                    addon:SendBugsToUser(recipient, data)
                end,
                OnShow = function(self)
                    self.button1:Disable()
                end,
                EditBoxOnTextChanged = function(self)
                    local t = self:GetText()
                    if t:len() > 2 and not t:find("%s") then
                        self:GetParent().button1:Enable()
                    else
                        self:GetParent().button1:Disable()
                    end
                end,
                enterClicksFirstButton = true,
                --OnCancel = function() show() end, -- Need to wrap it so we don't pass |self| as an error argument to show().
                preferredIndex = STATICPOPUP_NUMDIALOGS,
            }
        end

        if type(BugSackDB) ~= "table" then
            BugSackDB = {}
        end
        local sv = BugSackDB
        sv.profileKeys = nil
        sv.profiles = nil
        if type(sv.mute) ~= "boolean" then
            sv.mute = false
        end
        if type(sv.auto) ~= "boolean" then
            sv.auto = false
        end
        if type(sv.chatframe) ~= "boolean" then
            sv.chatframe = false
        end
        if type(sv.soundMedia) ~= "string" then
            sv.soundMedia = "BugSack: Fatality"
        end
        if type(sv.fontSize) ~= "string" then
            sv.fontSize = "GameFontHighlight"
        end
        if type(sv.altwipe) ~= "boolean" then
            sv.altwipe = false
        end
        if type(sv.useMaster) ~= "boolean" then
            sv.useMaster = false
        end
        addon.db = sv

        -- Make sure we grab any errors fired before bugsack loaded.
        local session = addon:GetErrors(BugGrabber:GetSessionId())
        if #session > 0 then
            onError()
        end

        if addon.RegisterComm then
            addon:RegisterComm("BugSack", "OnBugComm")
        end

        -- Set up our error event handler
        BugGrabber.RegisterCallback(addon, "BugGrabber_BugGrabbed", onError)

        SlashCmdList.BugSack = function(msg)
            msg = msg:lower()
            if msg == "show" then
                addon:OpenSack()
            else
                if InterfaceOptionsFrame_OpenToCategory then
                    InterfaceOptionsFrame_OpenToCategory(addonName)
                    InterfaceOptionsFrame_OpenToCategory(addonName)
                else
                    Settings.OpenToCategory(addon.settingsCategory.ID)
                end
            end
        end
        SLASH_BugSack1 = "/bugsack"

        self:SetScript("OnEvent", nil)
    end)
    eventFrame:RegisterEvent("ADDON_LOADED")
    addon.frame = eventFrame
end

-----------------------------------------------------------------------
-- API
--

function addon:UpdateDisplay()
    -- noop, hooked by displays
end

do
    local errors = {}
    function addon:GetErrors(sessionId)
        -- XXX I've never liked this function, maybe a BugGrabber redesign is in order,
        -- XXX where we have one subtable in the DB per session ID.
        if sessionId then
            wipe(errors)
            local db = BugGrabber:GetDB()
            for i, e in next, db do
                if sessionId == e.session then
                    errors[#errors + 1] = e
                end
            end
            return errors
        else
            return BugGrabber:GetDB()
        end
    end
end

do
    local function colorStack(ret)
        ret = tostring(ret) or "" -- Yes, it gets called with nonstring from somewhere /mikk
        ret = ret:gsub("[%.I][%.n][%.t][%.e][%.r]face/", "")
        ret = ret:gsub("%.?%.?%.?/?AddOns/", "")
        ret = ret:gsub("|([^chHr])", "||%1"):gsub("|$", "||") -- Pipes
        ret = ret:gsub("<(.-)>", "|cffffea00<%1>|r") -- Things wrapped in <>
        ret = ret:gsub("%[(.-)%]", "|cffffea00[%1]|r") -- Things wrapped in []
        ret = ret:gsub("([\"`'])(.-)([\"`'])", "|cff8888ff%1%2%3|r") -- Quotes
        ret = ret:gsub(":(%d+)([%S\n])", ":|cff00ff00%1|r%2") -- Line numbers
        ret = ret:gsub("([^/]+%.lua)", "|cffffffff%1|r") -- Lua files
        return ret
    end
    addon.ColorStack = colorStack

    local function colorLocals(ret)
        ret = tostring(ret) or "" -- Yes, it gets called with nonstring from somewhere /mikk
        ret = ret:gsub("[%.I][%.n][%.t][%.e][%.r]face/", "")
        ret = ret:gsub("%.?%.?%.?/?AddOns/", "")
        ret = ret:gsub("|(%a)", "||%1"):gsub("|$", "||") -- Pipes
        ret = ret:gsub("> %@(.-):(%d+)", "> @|cffeda55f%1|r:|cff00ff00%2|r") -- Files/Line Numbers of locals
        ret = ret:gsub("(%s-)([%a_%(][%a_%d%*%)]+) = ", "%1|cffffff80%2|r = ") -- Table keys
        ret = ret:gsub("= (%-?[%d%p]+)\n", "= |cffff7fff%1|r\n") -- locals: number
        ret = ret:gsub("= nil\n", "= |cffff7f7fnil|r\n") -- locals: nil
        ret = ret:gsub("= true\n", "= |cffff9100true|r\n") -- locals: true
        ret = ret:gsub("= false\n", "= |cffff9100false|r\n") -- locals: false
        ret = ret:gsub("= <(.-)>", "= |cffffea00<%1>|r") -- Things wrapped in <>
        return ret
    end
    addon.ColorLocals = colorLocals

    local errorFormat = "%dx %s"
    local errorFormatLocals = "%dx %s\n\nLocals:\n%s"
    function addon:FormatError(err)
        if not err.locals then
            local s = colorStack(tostring(err.message) .. (err.stack and "\n" .. tostring(err.stack) or ""))
            local l = colorLocals(tostring(err.locals))
            return errorFormat:format(err.counter or -1, s, l)
        else
            local s = colorStack(tostring(err.message) .. (err.stack and "\n" .. tostring(err.stack) or ""))
            local l = colorLocals(tostring(err.locals))
            return errorFormatLocals:format(err.counter or -1, s, l)
        end
    end
end

function addon:Reset()
    BugGrabber:Reset()
    self:UpdateDisplay()
    print(L["All stored bugs have been exterminated painfully."])
end

-- Sends the current session errors to another player using AceComm-3.0
function addon:SendBugsToUser(player, session)
    if type(player) ~= "string" or player:trim():len() < 2 then
        error(L["Player needs to be a valid name."])
    end
    if not self.Serialize then
        return
    end

    local errors = self:GetErrors(session)
    if not errors or #errors == 0 then
        return
    end
    local sz = self:Serialize(errors)
    self:SendCommMessage("BugSack", sz, "WHISPER", player, "BULK")

    print(L["%d bugs have been sent to %s. He must have BugSack to be able to examine them."]:format(#errors, player))
end

function addon:OnBugComm(prefix, message, _, sender)
    if prefix ~= "BugSack" or not self.Deserialize then
        return
    end

    local good, deSz = self:Deserialize(message)
    if not good then
        print(L["Failure to deserialize incoming data from %s."]:format(sender))
        return
    end

    -- Store recieved errors in the current session database with a source set to the sender
    local s = BugGrabber:GetSessionId()
    for i, err in next, deSz do
        err.source = sender
        err.session = s
        BugGrabber:StoreError(err)
    end

    print(L["You've received %d bugs from %s."]:format(#deSz, sender))

    wipe(deSz)
    deSz = nil
end

--[[

do
	local commFormat = "1#%s#%s"
	local function transmit(command, target, argument)
		SendAddonMessage("BugGrabber", commFormat:format(command, argument), "WHISPER", target)
	end

	local retrievedErrors = {}
	function addon:GetErrorByPlayerAndID(player, id)
		if player == playerName then return self:GetErrorByID(id) end
		-- This error was linked by someone else, we need to retrieve it from them
		-- using the addon communication channel.
		if retrievedErrors[id] then return retrievedErrors[id] end
		transmit("FETCH", player, id)
		print(L.ERROR_INCOMING:format(id, player))
	end

	local fakeAddon, comm, serializer = nil, nil, nil
	local function commBugCatcher(prefix, message, distribution, sender)
		local good, deSz = fakeAddon:Deserialize(message)
		if not good then
			print("damnit")
			return
		end
		retrievedErrors[deSz.originalId] = deSz

	end
	local function hasTransmitFacilities()
		if fakeAddon then return true end
		if not serializer then serializer = LibStub("AceSerializer-3.0", true) end
		if not comm then comm = LibStub("AceComm-3.0", true) end
		if comm and serializer then
			fakeAddon = {}
			comm:Embed(fakeAddon)
			serializer:Embed(fakeAddon)
			fakeAddon:RegisterComm("BGBug", commBugCatcher)
			return true
		end
	end

	function frame:CHAT_MSG_ADDON(event, prefix, message, distribution, sender)
		if prefix ~= "BugGrabber" then return end
		local version, command, argument = strsplit("#", message)
		if tonumber(version) ~= 1 or not command then return end
		if command == "FETCH" then
			local errorObject = addon:GetErrorByID(argument)
			if errorObject then
				if hasTransmitFacilities() then
					errorObject.originalId = argument
					local sz = fakeAddon:Serialize(errorObject)
					fakeAddon:SendCommMessage("BGBug", sz, "WHISPER", sender, "BULK")
				else
					-- We can only transmit a gimped and sanitized message
					transmit("BUG", sender, errorObject.message:sub(1, 240):gsub("#", ""))
				end
			else
				transmit("FAIL", sender, argument)
			end
		elseif command == "FAIL" then
			print(L.ERROR_FAILED_FETCH:format(argument, sender))
		elseif command == "BUG" then
			print(L.CRIPPLED_ERROR:format(sender, argument))
		end
	end
end]]

